#include <crt_util.h>

#if (TARGET_OS == OS_WIN)

#include <winsock2.h>
int _gettimeofday(struct timeval* tv, struct timezone* tz)
{
    const long long EPOCHFILETIME = (116444736000000000LL);
    FILETIME ft;
    LARGE_INTEGER li;
    long long t;

    /* This implementation doesn't support the timezone parameter. That's Ok,
     * as at present polipo always passed NULL as the second arg. We
     * also need to make sure that we have at least 8 bytes of space to
     * do the math in - otherwise there will be overflow errors.
     */

    if (tv) {
        GetSystemTimeAsFileTime(&ft);
        li.LowPart = ft.dwLowDateTime;
        li.HighPart = ft.dwHighDateTime;
        t = li.QuadPart;    /* In 100-nanosecond intervals */
        t -= EPOCHFILETIME; /* Offset to the Epoch time */
        t /= 10;            /* In microseconds */
        tv->tv_sec = (long)(t / 1000000);
        tv->tv_usec = (long)(t % 1000000);
    }
    return 0;
}

#endif

#if (TARGET_OS == OS_WIN)

#elif (TARGET_OS == OS_POSIX)

unsigned long GetTickCount()
{
    struct timespec ts;

#ifdef CLOCK_MONOTONIC_FAST
    clockid_t id = CLOCK_MONOTONIC_FAST;
#else
#if defined(CLOCK_MONOTONIC_RAW)
    clockid_t id = CLOCK_MONOTONIC_RAW;
#else
#if defined(CLOCK_MONOTONIC)
    clockid_t id = CLOCK_MONOTONIC;
#endif
#endif
#endif // CLOCK_MONOTONIC_FAST

    clock_gettime(id, &ts);

    return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}

#elif (TARGET_OS == OS_MACH)

unsigned long GetTickCount(void)
{
    struct timespec ts;
    if (__builtin_available(macOS 10.12, *)) {
        if (__builtin_available(iOS 10.0, *)) {
            clock_gettime(_CLOCK_MONOTONIC, &ts);
        } else {
            // Fallback on earlier versions
        }
    } else {
        // Fallback on earlier versions
    }
    return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}

#elif (TARGET_OS == OS_UNIX)

unsigned long GetTickCount(void)
{
    return 1000000;
}
#endif

uint64_t _gettimeofday_s(void)
{
    struct timeval tv;
#if (TARGET_OS == OS_WIN)
    _gettimeofday(&tv, NULL);
#else
    gettimeofday(&tv, NULL);
#endif
    return tv.tv_sec + (tv.tv_usec / 1000000);
}
uint64_t _gettimeofday_ms(void)
{
    struct timeval tv;
#if (TARGET_OS == OS_WIN)
    _gettimeofday(&tv, NULL);
#else
    gettimeofday(&tv, NULL);
#endif
    return tv.tv_sec * (unsigned long long)1000 + tv.tv_usec / 1000;
}
uint64_t _gettimeofday_us(void)
{
    struct timeval tv;
#if (TARGET_OS == OS_WIN)
    _gettimeofday(&tv, NULL);
#else
    gettimeofday(&tv, NULL);
#endif
    return tv.tv_sec * (unsigned long long)1000000 + tv.tv_usec;
}

uint64_t _getclock_ms(void)
{
    int rc;
#if (TARGET_OS == OS_WIN)

    LARGE_INTEGER tps;
    LARGE_INTEGER time;
    double tpms;
    QueryPerformanceFrequency(&tps);
    QueryPerformanceCounter(&time);
    tpms = (double)(tps.QuadPart / 1000);
    return (uint64_t)(time.QuadPart / tpms);

#elif (TARGET_OS == OS_POSIX)

    struct timespec tv;

#ifdef CLOCK_MONOTONIC_FAST
    clockid_t id = CLOCK_MONOTONIC_FAST;
#else
#if defined(CLOCK_MONOTONIC_RAW)
    clockid_t id = CLOCK_MONOTONIC_RAW;
#else
#if defined(CLOCK_MONOTONIC)
    clockid_t id = CLOCK_MONOTONIC;
#endif
#endif
#endif

    rc = clock_gettime(id, &tv);
    return tv.tv_sec * (uint64_t)1000 + tv.tv_nsec / 1000000;

#elif (TARGET_OS == OS_MACH)

    mach_timebase_info_data_t nn_clock_timebase_info;
    uint64_t ticks;
    /*  If the global timebase info is not initialised yet, init it. */
    if (uu_slow(!nn_clock_timebase_info.denom))
        mach_timebase_info(&nn_clock_timebase_info);

    ticks = mach_absolute_time();
    return ticks * nn_clock_timebase_info.numer / nn_clock_timebase_info.denom / 1000000;
#else
    struct timeval tv;
    rc = gettimeofday(&tv, NULL);
    return tv.tv_sec * (uint64_t)1000 + tv.tv_usec / 1000;
#endif
}

struct tm* _localtime_t(const time_t* timep, struct tm* result)
{
#if (TARGET_OS == OS_WIN)
    return (localtime_s(result, timep) == 0) ? result : NULL;
#elif (TARGET_OS == OS_POSIX)
    return localtime_r(timep, result);
#elif (TARGET_OS == OS_MACH)
    return localtime_r(timep, result);
#elif (TARGET_OS == OS_UNIX)
    return localtime_r(timep, result);
#endif
}

#define TIME_FORMAT "%Y/%m/%d|%H:%M:%S"

int get_time_t(char* tm, unsigned long len)
{
    rc_error(len > 1, S_ERROR);
    time_t seconds = 0;
    size_t rc = 0;
    struct tm res;
    s_memset(&res, 0, sizeof(struct tm));

    seconds = time(NULL);

    _localtime_t(&seconds, &res);

    rc = strftime(tm, len - 1, TIME_FORMAT, &res);
    return rc > 0 ? S_SUCCESS : S_ERROR;
}

#define MAX_SECONDS_IN_MSEC_LONG (((LONG_MAX)-999) / 1000)

long tv_to_msec(const struct timeval* tv)
{
    rc_error(tv != NULL, -1);

    if (tv->tv_usec > 1000000 || tv->tv_sec > MAX_SECONDS_IN_MSEC_LONG)
        return -1;

    return (tv->tv_sec * 1000) + ((tv->tv_usec + 999) / 1000);
}

int timerval_add(struct timeval* utv, struct timeval* ttv, struct timeval* vtv)
{
    rc_error(utv != NULL, S_ERROR);
    rc_error(ttv != NULL, S_ERROR);

    vtv->tv_sec = utv->tv_sec + ttv->tv_sec;
    vtv->tv_usec = utv->tv_usec + ttv->tv_usec;
    if (vtv->tv_usec >= 1000000) {
        vtv->tv_sec++;
        vtv->tv_usec -= 1000000;
    }

    return S_SUCCESS;
}
int timerval_sub(struct timeval* utv, struct timeval* ttv, struct timeval* vtv)
{
    rc_error(utv != NULL, S_ERROR);
    rc_error(ttv != NULL, S_ERROR);

    vtv->tv_sec = utv->tv_sec - ttv->tv_sec;
    vtv->tv_usec = utv->tv_usec - ttv->tv_usec;
    if (vtv->tv_usec < 0) {
        vtv->tv_sec--;
        vtv->tv_usec += 1000000;
    }

    return S_SUCCESS;
}
int timerval_cmp(struct timeval* utv, struct timeval* ttv)
{
    rc_error(utv != NULL, tv_cmp_err);
    rc_error(ttv != NULL, tv_cmp_err);

    if (utv->tv_sec == ttv->tv_sec) {
        if (utv->tv_usec == ttv->tv_usec)
            return tv_cmp_suc;
        else if (utv->tv_usec > ttv->tv_usec)
            return tv_cmp_great;
        else if (utv->tv_usec < ttv->tv_usec)
            return tv_cmp_less;
    } else if (utv->tv_sec > ttv->tv_sec) {
        return tv_cmp_great;
    } else if (utv->tv_sec < ttv->tv_sec) {
        return tv_cmp_less;
    }

    return tv_cmp_err;
}
